﻿using Microsoft.CSharp;
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;

namespace NFox.Pycad
{
    public static class DynamicCompiler
    {

        public static List<string> Mgdlls;

        #region SysCmds

        public static Dictionary<string, Action> SystemCommands { get; }
            = new Dictionary<string, Action>();

        public static void AddSystemCommand(string name, Action action)
        {
            SystemCommands[name] = action;
        }

        public static bool BuildSystemAssembly()
        {

            //动态编译系统命令集

            //声明代码的部分
            CodeCompileUnit compunit = new CodeCompileUnit();
            CodeNamespace ns = new CodeNamespace("NFox.Pycad");
            compunit.Namespaces.Add(ns);

            //引用命名空间
            ns.Imports.Add(new CodeNamespaceImport("System"));
            ns.Imports.Add(new CodeNamespaceImport("Autodesk.AutoCAD.Runtime"));

            //在命名空间下添加一个类
            CodeTypeDeclaration cmdsclass = new CodeTypeDeclaration("Commands");
            ns.Types.Add(cmdsclass);

            foreach (var cmd in SystemCommands)
                cmdsclass.Members.Add(BuildCmdMethod(cmd.Key));

            //生成驻留内存的动态程序集
            CompilerParameters pars = new CompilerParameters();
            pars.CompilerOptions = "/target:library /optimize";
            pars.GenerateExecutable = false;
            pars.GenerateInMemory = true;

            //添加引用
            var asslst = pars.ReferencedAssemblies;
            asslst.Add("System.dll");
            asslst.Add("System.Core.dll");
            asslst.Add("Microsoft.CSharp.dll");
            asslst.Add(DirectoryEx.GetFile("NFox.Pycad.Root.dll").FullName);
            asslst.Add(DirectoryEx.GetFile("NFox.Pycad.Core.dll").FullName);
            foreach (string name in Mgdlls)
                asslst.Add(name);

            //编译并加载
            CSharpCodeProvider cprovider = new CSharpCodeProvider();
            CompilerResults cr =
                cprovider.CompileAssemblyFromDom(pars, compunit);
            return cr.Errors.Count == 0;
        }

        private static CodeMemberMethod BuildCmdMethod(string name)
        {

            CodeMemberMethod method =
                new CodeMemberMethod { Name = name };

            //设置命令名
            var args = new List<CodeAttributeArgument>();
            args.Add(
                new CodeAttributeArgument(
                    new CodePrimitiveExpression(name)));
            method.CustomAttributes.Add(
                new CodeAttributeDeclaration(
                    "CommandMethod",
                    args.ToArray()));
            method.Attributes = MemberAttributes.Static | MemberAttributes.Public;

            method.Statements.Add(
                new CodeSnippetStatement(
                    $"NFox.Pycad.Core.Modules.pye.runsyscmd(\"{name}\");"));
            return method;

        }

        #endregion

        #region UserCmds

        public static void ClearCommands(Dictionary<string, dynamic> cmds)
        {
            string format = "acin.Utils.RemoveCommand(\"NFox.Pycad\", \"{0}\")";
            foreach (var cmd in cmds)
                Root.Application.Engine.Execute(string.Format(format, cmd.Value.Name));
        }

        public static void AddCommands(Dictionary<string, dynamic> cmds)
        {
            foreach (var cmd in cmds)
            {
                Root.Application.Engine.Execute(string.Format("tmpflags = System.Enum.Parse(acrx.CommandFlags, \'{0}\')", cmd.Value.Flags));
                string format = "acin.Utils.AddCommand(\"NFox.Pycad\", \"{0}\", \"{0}\", tmpflags, lambda:pye.runcmd(\"{0}\"))";
                Root.Application.Engine.Execute(string.Format(format, cmd.Value.Name));
            }
        }

        private static int _tempIndex;

        public static bool BuildUserAssembly(Dictionary<string, dynamic> cmds, Dictionary<string, dynamic> lisps)
        {

            if (cmds.Count == 0 && lisps.Count == 0)
                return true;

            //按py文件的内容生成对应的动态程序集

            //声明代码的部分
            CodeCompileUnit compunit = new CodeCompileUnit();
            CodeNamespace ns = new CodeNamespace($"NFox.Pycad{_tempIndex++}");
            compunit.Namespaces.Add(ns);

            //引用命名空间
            ns.Imports.Add(new CodeNamespaceImport("System"));
            ns.Imports.Add(new CodeNamespaceImport("NFox.Pycad.Core"));
            ns.Imports.Add(new CodeNamespaceImport("Autodesk.AutoCAD.Runtime"));
            ns.Imports.Add(new CodeNamespaceImport("Autodesk.AutoCAD.DatabaseServices"));

            //在命名空间下添加一个类
            CodeTypeDeclaration cmdsclass = new CodeTypeDeclaration("Commands");
            ns.Types.Add(cmdsclass);

            //为每个py函数按装饰器生成命令
            int i = 0;
            foreach (var cmd in cmds)
            {
                var method = BuildCmdMethod(cmd.Value, i++);
                cmdsclass.Members.Add(method);
            }

            //为每个py函数按装饰器生成lisp函数
            foreach (var lisp in lisps)
            {
                var method = BuildLispMethod(lisp.Value, i++);
                cmdsclass.Members.Add(method);
            }

            //生成驻留内存的动态程序集
            CompilerParameters pars = new CompilerParameters();
            pars.CompilerOptions = "/target:library /optimize";
            pars.GenerateExecutable = false;
            pars.GenerateInMemory = true;

            //添加引用
            var asslst = pars.ReferencedAssemblies;
            asslst.Add("System.dll");
            asslst.Add("System.Core.dll");
            asslst.Add("Microsoft.CSharp.dll");
            asslst.Add(DirectoryEx.GetFile("NFox.Pycad.Core.dll").FullName);
            foreach (string name in Mgdlls)
                asslst.Add(name);

            //编译并加载
            CSharpCodeProvider cprovider = new CSharpCodeProvider();
            CompilerResults cr =
                cprovider.CompileAssemblyFromDom(pars, compunit);
            return cr.Errors.Count == 0;

        }

        private static CodeMemberMethod BuildCmdMethod(dynamic cmd, int index)
        {

            CodeMemberMethod method =
                new CodeMemberMethod { Name = $"Cmd_{index}" };
            var args = new List<CodeAttributeArgument>();

            if ((int)cmd.Flags != 0)
                args.Add(
                    new CodeAttributeArgument(
                        new CodeCastExpression(
                            "CommandFlags",
                            new CodePrimitiveExpression((int)cmd.Flags))));

            //设置命令名
            args.Insert(0,
                new CodeAttributeArgument(
                    new CodePrimitiveExpression(cmd.Name)));
            method.CustomAttributes.Add(
                new CodeAttributeDeclaration(
                    "CommandMethod",
                    args.ToArray()));
            method.Attributes = MemberAttributes.Static | MemberAttributes.Public;

            method.Statements.Add(
                new CodeSnippetStatement(
                    $"NFox.Pycad.Core.Modules.pye.runcmd(\"{cmd.Name}\");"));
            return method;

        }

        private static CodeMemberMethod BuildLispMethod(dynamic lisp, int index)
        {

            CodeMemberMethod method =
                new CodeMemberMethod { Name = $"Lisp_{index}" };
            var args = new List<CodeAttributeArgument>();

            //设置命令名
            args.Add(
                new CodeAttributeArgument(
                    new CodePrimitiveExpression(lisp.Name)));
            method.CustomAttributes.Add(
                new CodeAttributeDeclaration(
                    "LispFunction",
                    args.ToArray()));
            method.Attributes = MemberAttributes.Static | MemberAttributes.Public;
            method.Parameters.Add(
                new CodeParameterDeclarationExpression(
                    "ResultBuffer", "rb"));
            method.ReturnType = new CodeTypeReference("Object");

            method.Statements.Add(
                new CodeSnippetStatement(
                    $"return NFox.Pycad.Core.Modules.pye.invokelisp(\"{lisp.Name}\", rb);"));
            return method;

        }

        #endregion

        # region Release

        public static bool BuildReleaseAssembly(string dllpath, string releasepath)
        {

            //动态编译安装程序集
            CompilerParameters pars = new CompilerParameters();
            pars.EmbeddedResources.Add(releasepath);
            pars.GenerateExecutable = false;
            pars.GenerateInMemory = false;
            pars.OutputAssembly = dllpath;

            //声明代码的部分
            CodeCompileUnit compunit = new CodeCompileUnit();

            CodeNamespace ns = new CodeNamespace("NFox.Pycad.Setup");
            compunit.Namespaces.Add(ns);

            //引用命名空间
            ns.Imports.Add(new CodeNamespaceImport("Autodesk.AutoCAD.Runtime"));
            ns.Imports.Add(new CodeNamespaceImport("System"));
            ns.Imports.Add(new CodeNamespaceImport("System.Collections.Generic"));
            ns.Imports.Add(new CodeNamespaceImport("System.IO"));
            ns.Imports.Add(new CodeNamespaceImport("System.IO.Compression"));
            ns.Imports.Add(new CodeNamespaceImport("System.Reflection"));
            ns.Imports.Add(new CodeNamespaceImport("System.Xml.Linq"));

            //设置程序集特性
            compunit.AssemblyCustomAttributes.Add(
                new CodeAttributeDeclaration(
                    "Autodesk.AutoCAD.Runtime.ExtensionApplication",
                    new CodeAttributeArgument(
                        new CodeTypeOfExpression("NFox.Pycad.Setup.Application"))));

            //在命名空间下添加一个类
            CodeTypeDeclaration appclass = new CodeTypeDeclaration("Application");
            ns.Types.Add(appclass);
            appclass.BaseTypes.Add(new CodeTypeReference("IExtensionApplication"));

            //函数Initialize
            CodeMemberMethod method =
                new CodeMemberMethod { Name = "Initialize" };
            method.Attributes = MemberAttributes.Public;
            appclass.Members.Add(method);

            method.Statements.Add(
                new CodeVariableDeclarationStatement("String", "mainpath"));
            method.Statements.Add(
                new CodeVariableDeclarationStatement(
                    "Assembly", "ass",
                    new CodeSnippetExpression("Assembly.GetExecutingAssembly()")));
            method.Statements.Add(
                new CodeVariableDeclarationStatement(
                    "String", "resname",
                    new CodeSnippetExpression("ass.GetManifestResourceNames()[0]")));
            method.Statements.Add(
                new CodeVariableDeclarationStatement(
                    "Stream", "stream",
                    new CodeSnippetExpression("ass.GetManifestResourceStream(resname)")));
            method.Statements.Add(
                new CodeVariableDeclarationStatement(
                    "ZipArchive", "zip",
                    new CodeSnippetExpression("new ZipArchive(stream, ZipArchiveMode.Read)")));
            method.Statements.Add(
                new CodeVariableDeclarationStatement(
                    "Stream", "cstream",
                    new CodeSnippetExpression("zip.GetEntry(\"Update\\\\Config.xml\").Open()")));
            method.Statements.Add(
                new CodeVariableDeclarationStatement(
                    "XElement", "xe",
                    new CodeSnippetExpression("XElement.Load(cstream)")));
            method.Statements.Add(
                new CodeSnippetStatement("cstream.Dispose();"));
            method.Statements.Add(
                new CodeVariableDeclarationStatement(
                    "Version", "newver",
                    new CodeSnippetExpression("Version.Parse(xe.Attribute(\"Version\").Value)")));
            method.Statements.Add(
                new CodeVariableDeclarationStatement(
                    "RegistryKey", "rk",
                    new CodeSnippetExpression("Registry.CurrentUser.OpenSubKey(\"Software\", Microsoft.Win32.RegistryKeyPermissionCheck.ReadWriteSubTree)")));
            method.Statements.Add(
                new CodeVariableDeclarationStatement(
                    "RegistryKey", "nfoxrk",
                    new CodeSnippetExpression("rk.OpenSubKey(\"NFox\")")));
            method.Statements.Add(
                new CodeVariableDeclarationStatement(
                    "RegistryKey", "pycadrk",
                    new CodeSnippetExpression("rk.CreateSubKey(\"NFox\").CreateSubKey(\"Pycad\")")));

            var ifstat =
                new CodeConditionStatement(
                    new CodeSnippetExpression("nfoxrk == null || nfoxrk.OpenSubKey(\"Pycad\") == null"));
            method.Statements.Add(ifstat);

            ifstat.TrueStatements.Add(
                new CodeSnippetStatement("pycadrk.SetValue(\"Version\", newver.ToString());"));
            ifstat.TrueStatements.Add(
                new CodeVariableDeclarationStatement(
                    "String", "path",
                    new CodeSnippetExpression("\"C:\\\\Program Files\\\\Pycad\"")));
            ifstat.TrueStatements.Add(
                new CodeSnippetStatement("mainpath = path;"));

            ifstat.TrueStatements.Add(
                new CodeIterationStatement(
                    new CodeVariableDeclarationStatement(
                        "Int32", "i",
                        new CodeSnippetExpression("2")),
                    new CodeSnippetExpression("Directory.Exists(mainpath)"),
                    new CodeSnippetStatement("i++"),
                    new CodeSnippetStatement("mainpath = path + i;")));
            ifstat.TrueStatements.Add(
                new CodeSnippetStatement("pycadrk.SetValue(\"MainPath\", mainpath);"));
            ifstat.TrueStatements.Add(
                new CodeVariableDeclarationStatement(
                    "DirectoryInfo", "dir",
                    new CodeSnippetExpression("Directory.CreateDirectory(mainpath)")));
            ifstat.TrueStatements.Add(
                new CodeVariableDeclarationStatement(
                    "Stream", "pstream",
                    new CodeSnippetExpression("zip.GetEntry(\"Update\\\\Package.xml\").Open()")));
            ifstat.TrueStatements.Add(
                new CodeSnippetStatement("xe = XElement.Load(pstream);"));
            ifstat.TrueStatements.Add(
                new CodeSnippetStatement("pstream.Dispose();"));
            ifstat.TrueStatements.Add(
                new CodeSnippetStatement("CopyFiles(xe, dir, zip);"));

            ifstat.TrueStatements.Add(
                new CodeIterationStatement(
                    new CodeVariableDeclarationStatement(
                        "IEnumerator<ZipArchiveEntry>", "itor",
                        new CodeSnippetExpression("zip.Entries.GetEnumerator()")),
                    new CodeSnippetExpression("itor.MoveNext()"),
                    new CodeSnippetStatement(""),
                    new CodeSnippetStatement("ZipArchiveEntry ent = itor.Current;"),
                    new CodeConditionStatement(
                        new CodeSnippetExpression("ent.FullName.StartsWith(\"Update\\\\Projects\")"),
                        new CodeSnippetStatement("Stream source = ent.Open();"),
                        new CodeSnippetStatement("Stream target = File.OpenWrite(mainpath + \"\\\\\" + ent.FullName);"),
                        new CodeSnippetStatement("source.CopyTo(target);"),
                        new CodeSnippetStatement("target.Dispose();"),
                        new CodeSnippetStatement("source.Dispose();"))));
            ifstat.TrueStatements.Add(
                new CodeSnippetStatement("Assembly.LoadFrom(mainpath+ \"\\\\NFox.Pycad.dll\");"));

            ifstat.FalseStatements.Add(
                new CodeVariableDeclarationStatement(
                    "Version", "oldver",
                    new CodeSnippetExpression("Version.Parse(pycadrk.GetValue(\"Version\").ToString())")));
            ifstat.FalseStatements.Add(
                new CodeSnippetStatement("mainpath = pycadrk.GetValue(\"MainPath\").ToString();"));

            var ifstat2 =
                new CodeConditionStatement(
                    new CodeSnippetExpression("newver > oldver"));
            ifstat.FalseStatements.Add(ifstat2);

            ifstat2.TrueStatements.Add(
                new CodeIterationStatement(
                    new CodeVariableDeclarationStatement(
                        "IEnumerator<ZipArchiveEntry>", "itor",
                        new CodeSnippetExpression("zip.Entries.GetEnumerator()")),
                    new CodeSnippetExpression("itor.MoveNext()"),
                    new CodeSnippetStatement(""),
                    new CodeSnippetStatement("ZipArchiveEntry ent = itor.Current;"),
                    new CodeSnippetStatement("Stream source = ent.Open();"),
                    new CodeSnippetStatement("Stream target = File.OpenWrite(mainpath + \"\\\\\" + ent.FullName);"),
                    new CodeSnippetStatement("source.CopyTo(target);"),
                    new CodeSnippetStatement("target.Dispose();"),
                    new CodeSnippetStatement("source.Dispose();")));

            ifstat2.FalseStatements.Add(
                new CodeIterationStatement(
                    new CodeVariableDeclarationStatement(
                        "IEnumerator<ZipArchiveEntry>", "itor",
                        new CodeSnippetExpression("zip.Entries.GetEnumerator()")),
                    new CodeSnippetExpression("itor.MoveNext()"),
                    new CodeSnippetStatement(""),
                    new CodeSnippetStatement("ZipArchiveEntry ent = itor.Current;"),
                    new CodeConditionStatement(
                        new CodeSnippetExpression("ent.FullName.StartsWith(\"Update\\\\Projects\")"),
                        new CodeSnippetStatement("Stream source = ent.Open();"),
                        new CodeSnippetStatement("Stream target = File.OpenWrite(mainpath + \"\\\\\" + ent.FullName);"),
                        new CodeSnippetStatement("source.CopyTo(target);"),
                        new CodeSnippetStatement("target.Dispose();"),
                        new CodeSnippetStatement("source.Dispose();"))));

            method.Statements.Add(
                new CodeSnippetStatement("pycadrk.Dispose();"));
            method.Statements.Add(
                new CodeSnippetStatement("nfoxrk.Dispose();"));
            method.Statements.Add(
                new CodeSnippetStatement("rk.Dispose();"));
            method.Statements.Add(
                new CodeSnippetStatement("zip.Dispose();"));
            method.Statements.Add(
                new CodeSnippetStatement("stream.Dispose();"));

            //函数Terminate
            method =
                new CodeMemberMethod { Name = "Terminate" };
            method.Attributes = MemberAttributes.Public;
            appclass.Members.Add(method);

            //函数UnzipDllx
            method =
                new CodeMemberMethod { Name = "UnzipDllx" };
            method.Parameters.Add(
                new CodeParameterDeclarationExpression(
                    "Stream", "stream"));
            method.Parameters.Add(
                new CodeParameterDeclarationExpression(
                    "DirectoryInfo", "dir"));
            method.Attributes = MemberAttributes.Private;
            appclass.Members.Add(method);

            method.Statements.Add(
                new CodeVariableDeclarationStatement(
                    "ZipArchive", "zip",
                    new CodeSnippetExpression("new ZipArchive(stream, ZipArchiveMode.Read)")));
            method.Statements.Add(
                new CodeIterationStatement(
                    new CodeVariableDeclarationStatement(
                        "IEnumerator<ZipArchiveEntry>", "it",
                        new CodeSnippetExpression("zip.Entries.GetEnumerator()")),
                    new CodeSnippetExpression("it.MoveNext()"),
                    new CodeSnippetStatement(""),
                    new CodeSnippetStatement("ZipArchiveEntry ent = it.Current;"),
                    new CodeSnippetStatement("string path = dir.FullName + \"\\\\\" + ent.FullName;"),
                    new CodeSnippetStatement("string ddir = Path.GetDirectoryName(path);"),
                    new CodeConditionStatement(
                        new CodeSnippetExpression("!Directory.Exists(ddir)"),
                        new CodeSnippetStatement("Directory.CreateDirectory(ddir);")),
                    new CodeSnippetStatement("Stream source = ent.Open();"),
                    new CodeSnippetStatement("Stream target = File.Open(path, FileMode.Create);"),
                    new CodeSnippetStatement("source.CopyTo(target);"),
                    new CodeSnippetStatement("target.Dispose();"),
                    new CodeSnippetStatement("source.Dispose();")));
            method.Statements.Add(
                new CodeSnippetStatement("zip.Dispose();"));

            //函数CopyFiles
            method =
                new CodeMemberMethod { Name = "CopyFiles" };
            method.Parameters.Add(
                new CodeParameterDeclarationExpression(
                    "XElement", "xe"));
            method.Parameters.Add(
                new CodeParameterDeclarationExpression(
                    "DirectoryInfo", "dir"));
            method.Parameters.Add(
                new CodeParameterDeclarationExpression(
                    "ZipArchive", "zip"));
            method.Attributes = MemberAttributes.Private;
            appclass.Members.Add(method);

            var forstat =
                new CodeIterationStatement();
            method.Statements.Add(forstat);

            forstat.InitStatement =
                new CodeVariableDeclarationStatement(
                    "IEnumerator<XElement>", "it",
                    new CodeSnippetExpression("xe.Elements().GetEnumerator()"));
            forstat.TestExpression =
                new CodeSnippetExpression("it.MoveNext()");
            forstat.IncrementStatement =
                new CodeSnippetStatement("");
            forstat.Statements.Add(
                 new CodeVariableDeclarationStatement(
                    "XElement", "e",
                    new CodeSnippetExpression("it.Current")));

            ifstat = 
                new CodeConditionStatement(
                    new CodeSnippetExpression("e.Name.LocalName == \"Directory\""));
            forstat.Statements.Add(ifstat);

            ifstat.TrueStatements.Add(
                new CodeVariableDeclarationStatement(
                    "DirectoryInfo", "sdir",
                    new CodeSnippetExpression("dir.CreateSubdirectory(e.Attribute(\"Name\").Value)")));
            ifstat.TrueStatements.Add(
                new CodeSnippetStatement("CopyFiles(e, sdir, zip);"));

            ifstat.FalseStatements.Add(
                new CodeVariableDeclarationStatement(
                    "String", "name",
                    new CodeSnippetExpression("e.Attribute(\"Name\").Value")));

            var forstat2 =
                new CodeIterationStatement(
                    new CodeVariableDeclarationStatement(
                        "IEnumerator<ZipArchiveEntry>", "itor",
                        new CodeSnippetExpression("zip.Entries.GetEnumerator()")),
                    new CodeSnippetExpression("itor.MoveNext()"),
                    new CodeSnippetStatement(""),
                    new CodeSnippetStatement("ZipArchiveEntry ent = itor.Current;"));
            ifstat.FalseStatements.Add(forstat2);

            ifstat2 =
                new CodeConditionStatement(
                    new CodeSnippetExpression("ent.Name == name"));
            forstat2.Statements.Add(ifstat2);

            ifstat2.TrueStatements.Add(
                new CodeVariableDeclarationStatement(
                    "Stream", "source",
                    new CodeSnippetExpression("ent.Open()")));

            var ifstat3 =
                new CodeConditionStatement(
                    new CodeSnippetExpression("e.Attribute(\"Build\") != null && Boolean.Parse(e.Attribute(\"Build\").Value)"));
            ifstat2.TrueStatements.Add(ifstat3);

            ifstat3.TrueStatements.Add(
                new CodeSnippetStatement("UnzipDllx(source, dir);"));
            ifstat3.FalseStatements.Add(
                new CodeVariableDeclarationStatement(
                    "Stream", "target",
                    new CodeSnippetExpression("File.Open(dir.FullName + \"\\\\\" + name, FileMode.Create)")));
            ifstat3.FalseStatements.Add(
                new CodeSnippetStatement("source.CopyTo(target);"));
            ifstat3.FalseStatements.Add(
                new CodeSnippetStatement("target.Dispose();"));

            ifstat2.TrueStatements.Add(
                new CodeSnippetStatement("source.Dispose();"));
            ifstat2.TrueStatements.Add(
                new CodeSnippetStatement("break;"));

            //添加引用
            var asslst = pars.ReferencedAssemblies;
            asslst.Add("System.dll");
            asslst.Add("System.Core.dll");
            asslst.Add("Microsoft.CSharp.dll");
            asslst.Add("System.Xml.dll");
            asslst.Add("System.Xml.Linq.dll");
            asslst.Add("System.IO.Compression.dll");
            foreach (string name in Mgdlls)
                asslst.Add(name);

            //编译并加载
            CSharpCodeProvider cprovider = new CSharpCodeProvider();
            CompilerResults cr =
                cprovider.CompileAssemblyFromDom(pars, compunit);
            File.Delete(releasepath);
            return cr.Errors.Count == 0;
        }

    #endregion

    }

}
